/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */

package cn.hermit.config;

import cn.hermit.util.ReflectionUtils;
import cn.hermit.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author JackGao 高建国
 * @description //TODO 设计说明
 * @date 3/6/2017
 */
public class Config {

    private static final Logger logger = Logger.getLogger(Config.class.getName());

    public static String KEY_CONFIG_LOCATION = "hermit.config.location";

    private static Properties configProperties = new Properties();

    static {
        Properties sysProperties = System.getProperties();
        load(sysProperties);
        String configLocation = sysProperties.getProperty(KEY_CONFIG_LOCATION);
        try {
            loadFromConfigLocation(configLocation);
        } catch (IOException e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    public static void load(URL... url) throws IOException {
        if (url == null || url.length == 0) {
            return;
        }
        Properties properties = new Properties();
        for (URL u : url) {
            InputStream is = null;
            try {
                is = u.openStream();
                properties.load(new InputStreamReader(is, Charset.defaultCharset()));
                properties.load(is);
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        }
        load(properties);
    }

    public static void load(Properties properties) {
        if (properties == null) {
            return;
        }
        for (String name : properties.stringPropertyNames()) {
            load(name, properties.getProperty(name));
        }
    }

    public static void load(String name, String value) {
        if (StringUtils.isBlank(name) || configProperties.containsKey(name)) {
            return;
        }
        if (!StringUtils.isBlank(value) && value.startsWith("${")) {
            value = configProperties.getProperty(value.substring(2, value.length() - 1));
        }
        if (value == null) {
            value = "";
        }
        configProperties.setProperty(name, value);
    }

    public static void loadFromArguments(String... args) {
        if (args == null || args.length == 0) {
            return;
        }
        String key = null;
        int idx = -1;
        for (String arg : args) {
            int length = arg.length();
            idx = arg.indexOf('-');
            if (idx == length - 1) {
                continue;
            }
            if (idx == -1) {
                if (key != null) {
                    load(key, arg);
                    key = null;
                }
            } else {
                if (arg.charAt(idx + 1) == '-') {
                    if (idx == length - 2) {
                        continue;
                    }
                    int idx0 = arg.indexOf('=');
                    if (idx0 == length - 1) {
                        continue;
                    }
                    if (idx0 == -1) {
                        key = arg.substring(idx + 2);
                    } else {
                        key = arg.substring(idx + 2, idx0);
                        load(key, arg.substring(idx0 + 1));
                    }
                } else {
                    key = arg.substring(idx + 1);
                }
            }
        }
    }

    public static void loadFromConfigLocation(String... configLocation) throws IOException {
        if (configLocation == null || configLocation.length == 0) {
            return;
        }
        if (configLocation.length == 1) {
            if (configLocation[0] == null) {
                return;
            }
            if (configLocation[0].indexOf(',') != -1) {
                configLocation = configLocation[0].split(",");
            }
        }
        List<URL> urlList = new ArrayList<>(configLocation.length);
        for (int i = 0; i < configLocation.length; i++) {
            if (StringUtils.isBlank(configLocation[i])) {
                continue;
            }
            urlList.add(new URL(configLocation[i]));
        }
        load(urlList.toArray(new URL[]{}));
    }

    public static String getProperty(String name, String defaultVal) {
        if (name == null) return null;
        if (name.startsWith("${")) {
            name = name.substring(2, name.length() - 1);
        }
        return configProperties.getProperty(name, defaultVal);
    }

    public static String getProperty(String name) {
        return getProperty(name, null);
    }

    public static <T> T getProperties(String prefix, Class<T> propertiesClass) {
        T properties = null;
        try {
            properties = propertiesClass.newInstance();
            populateProperties(prefix, properties);
        } catch (ReflectiveOperationException e) {
            //ignored
        }
        return properties;
    }

    public static void populateProperties(String prefix, Object properties) {
        try {
            Field[] fields = properties.getClass().getDeclaredFields();
            for (Field field : fields) {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    continue;
                }
                Class<?> fieldType = field.getType();
                String name = field.getName();
                String key = prefix + '.' + name;
                Object fieldValue = ReflectionUtils.getFieldValue(field, properties);
                if (Collection.class.isAssignableFrom(fieldType)) {
                    String value = getProperty(key);
                    if(StringUtils.isBlank(value)) {
                        continue;
                    }
                    if (fieldValue == null) {
                        if (List.class == fieldType) {
                            fieldValue = new ArrayList<>();
                        } else if (Set.class == fieldType) {
                            fieldValue = new LinkedHashSet<>();
                        } else {
                            fieldValue = fieldType.newInstance();
                        }
                    }
                    Class[] genericClasses = ReflectionUtils.getGenericClasses(field.getGenericType());
                    if (genericClasses == null || genericClasses.length == 0) {
                        String[] valueArr = value.split(",");
                        for(String val : valueArr) {
                            if(StringUtils.isBlank(val)) {
                                continue;
                            }
                            val = val.trim();
                            ((Collection) fieldValue).add(val);
                        }
                    } else {
                        String[] valueArr = value.split(",");
                        for(String val : valueArr) {
                            if(StringUtils.isBlank(val)) {
                                continue;
                            }
                            val = val.trim();
                            Object realValue = ReflectionUtils.getValue(val, genericClasses[0]);
                            ((Collection) fieldValue).add(realValue);
                        }
                    }
                } else if (Map.class.isAssignableFrom(fieldType)) {
                    if (field == null) {
                        if (Map.class == fieldType) {
                            fieldValue = new LinkedHashMap<>();
                        } else {
                            fieldValue = fieldType.newInstance();
                        }
                    }
                    Class[] genericClasses = ReflectionUtils.getGenericClasses(field.getGenericType());
                    int keyLength = key.length();
                    for (Object keyObj : configProperties.keySet()) {
                        String propName = (String) keyObj;
                        if (propName.startsWith(key)) {
                            if (propName.length() == keyLength) {
                                continue;
                            }
                            if (genericClasses == null || genericClasses.length == 0) {
                                String propVal = getProperty(propName);
                                if(StringUtils.isBlank(propVal)) {
                                    continue;
                                }
                                ((Map) fieldValue).put(propName.substring(key.length() + 1), propVal);
                            } else {
                                if (String.class != genericClasses[0]) {
                                    continue;
                                }

                                Object realValue = null;
                                if(ReflectionUtils.isBaseType(genericClasses[1])) {
                                    String propVal = getProperty(propName);
                                    if(StringUtils.isBlank(propVal)) {
                                        continue;
                                    }
                                    realValue = ReflectionUtils.getValue(propVal, genericClasses[1]);
                                    ((Map) fieldValue).put(propName.substring(key.length() + 1), realValue);
                                } else {
                                    String subKey = propName.substring(key.length() + 1);
                                    int idx = subKey.indexOf('.');
                                    if(idx == -1) {
                                        continue;
                                    }
                                    subKey = subKey.substring(0, idx);
                                    realValue = ((Map) fieldValue).get(subKey);
                                    if(realValue == null) {
                                        realValue = genericClasses[1].newInstance();
                                        populateProperties(key + '.' + subKey, realValue);
                                    }
                                    ((Map) fieldValue).put(subKey, realValue);
                                }

                            }
                        }
                    }
                } else if (fieldType.getName().startsWith("java")) {
                    String value = getProperty(key);
                    if(StringUtils.isBlank(value)) {
                        continue;
                    }
                    fieldValue = ReflectionUtils.getValue(value, fieldType);
                } else {
                    String value = getProperty(key);
                    if(StringUtils.isBlank(value)) {
                        continue;
                    }
                    if (fieldValue == null) {
                        fieldValue = fieldType.newInstance();
                    }
                    populateProperties(key, fieldValue);
                }
                if (fieldValue == null) {
                    continue;
                }
                ReflectionUtils.setFieldValue(field, fieldValue, properties);
            }
        } catch (ReflectiveOperationException e) {
            //ignored
        }
    }

}
